#include "Terrain.h"
#include <math.h>
#include <assert.h>
#include "TexMgr_Ogre.h"

#define MAX_DELTA 255
#define MIN_DELTA 0
#define TIMES_OF_ITER 128

template<class T>
T GetRandomRange(T iMin, T iMax)
{
	return ( iMin+( iMax-iMin )*( ( T )rand( ) )/( ( T )RAND_MAX ) );
	//return rand()*(iMax - iMin) / (RAND_MAX + iMin);
}

template<typename T> 
void NormTerrain(T* terrainBuff, int Count)
{
	float fMax,fMin, fH;
	fMax = terrainBuff[0];
	fMin = terrainBuff[0];
	for(int i = 0; i< Count; ++i)
	{
		if(terrainBuff[i] > fMin)
			fMax = terrainBuff[i];
		else if(terrainBuff[i]<fMin)
			fMin = terrainBuff[i];
	}
	if(fMax <= fMin) return;

	fH = fMax - fMin;
	for(int i = 0; i < Count; ++i)
	{
		terrainBuff[i] = ((terrainBuff[i] - fMin)/fH)*MAX_DELTA;
	}
}

#pragma region //Mid-point Algorithm have some issue, delay to implement >_<
//CMidPointTerrain::CMidPointTerrain()
//{
//	m_iSize = 0;
//	fTerrainBuff = NULL;
//	m_fHeightScale = 1.0f;
//}
//
//CMidPointTerrain::~CMidPointTerrain()
//{
//	if(fTerrainBuff)
//	{
//		delete[] fTerrainBuff;
//	}
//}
//
//bool CMidPointTerrain::LoadHeightMap( unsigned seeds, int iSize )
//{
//	fTerrainBuff = new float[iSize*iSize];
//	memset(fTerrainBuff, 0, iSize*iSize*sizeof(float));
//	m_iSize = iSize;
//	srand( seeds );
//	MidPointSetup(0,0,iSize - 1,iSize - 1,(iSize-1)/2 ,0.75f);
//	return true;
//}
//
//void CMidPointTerrain::MidPointSetup( int left, int top, int width, int height, float fDeltaH,float fRoughness )
//{
//	if(width == 0 || height == 0)
//		return;
//
//	//diamond step
//	float e = (fTerrainBuff[top*m_iSize + left] 
//	+ fTerrainBuff[top*m_iSize + left + width] 
//	+ fTerrainBuff[(top + height)*m_iSize]
//	+ fTerrainBuff[(top + height)*m_iSize + width])/4
//	+ GetRandomRange<float>(-fDeltaH*0.5f, fDeltaH*0.5f);
//
//	fTerrainBuff[(top + height/2)*m_iSize + left + width/2] = e;
//
//	//square step
//	
//	CalcSquareStep(left, top, width, height, fDeltaH);
//	
//	//calc coefficient for height
//	fDeltaH *= pow(2.0f, -1*fRoughness);
//
//	//recurse
//	//lt rec 
//	MidPointSetup(left, top, width/2, height/2, fDeltaH, fRoughness);
//	//rt rec
//	MidPointSetup(left + width/2, top, width/2, height/2, fDeltaH, fRoughness);
//	//lb rec
//	MidPointSetup(left, top + height/2, width/2, height/2, fDeltaH, fRoughness);
//	//rb rec
//	MidPointSetup(left + width/2, top + height/2, width/2, height/2, fDeltaH, fRoughness);
//}
//
//void CMidPointTerrain::CalcSquareStep( int left, int top, int width, int height, float fDeltaH )
//{
//	int up = top*m_iSize + left + width/2;
//	int down = (top+height)*m_iSize + left + width/2;
//	int lft = (top+height/2)*m_iSize + left;
//	int rgt = (top+height/2)*m_iSize + left + width;
//
//	float f = (top - height/2) > 0 ? fTerrainBuff[(top - height/2)*m_iSize + left + width/2] : fTerrainBuff[(top + height/2)*m_iSize + left + width/2];
//	float j = fTerrainBuff[(top + height/2)*m_iSize + left + width/2];
//	float g = fTerrainBuff[top*m_iSize + left];
//	float h = fTerrainBuff[top*m_iSize + left + width];
//	fTerrainBuff[up]	= (f + j + g + h)/4.0f + GetRandomRange<float>(-fDeltaH*0.5f, fDeltaH*0.5f);
//
//	f = j;
//	j = (top + height + height/2) < m_iSize ? fTerrainBuff[(top + height + height/2)*m_iSize + left + width/2] : fTerrainBuff[(top + height/2)*m_iSize + left + width/2];
//	g = fTerrainBuff[(top+height)*m_iSize + left];
//	h = fTerrainBuff[top*m_iSize + left + width];
//	fTerrainBuff[down]	= (f + j + g + h)/4.0f + GetRandomRange<float>(-fDeltaH*0.5f, fDeltaH*0.5f);
//
//	f = fTerrainBuff[top*m_iSize + left];
//	j = fTerrainBuff[(top+height)*m_iSize + left];
//	g = left == 0 ? fTerrainBuff[(top + height/2)*m_iSize + left + width/2] : fTerrainBuff[(top + height/2)*m_iSize + left - width/2];
//	h = fTerrainBuff[(top + height/2)*m_iSize + left + width/2];
//	fTerrainBuff[lft]	= (f + j + g + h)/4.0f + GetRandomRange<float>(-fDeltaH*0.5f, fDeltaH*0.5f);
//
//	f = fTerrainBuff[top*m_iSize + left + width];
//	j = fTerrainBuff[(top+height)*m_iSize + left + width];
//	g = fTerrainBuff[(top + height/2)*m_iSize + left + width/2];
//	h = (left + width + width/2) < m_iSize ? fTerrainBuff[(top + height/2)*m_iSize + left + width + width/2] : g;
//	fTerrainBuff[rgt]	= (f + j + g + h)/4.0f + GetRandomRange<float>(-fDeltaH*0.5f, fDeltaH*0.5f);
//}
#pragma endregion


CHeightMapTerrain::CHeightMapTerrain()
{
	m_iSize = 0;
	m_fScale = 1;
	m_texMgr = 0;
}

CHeightMapTerrain::~CHeightMapTerrain()
{
	ClearTerrainData();
}

bool CHeightMapTerrain::InitTerrainData(const char* fileName, int terrainSize)
{
	if (m_heightData.ucData)
	{
		ClearTerrainData();
	}
	m_heightData.ucData = new unsigned char[terrainSize*terrainSize];
	if(m_heightData.ucData == NULL)
	{
		std::cout<<"couldn't allocate memory for "<<fileName<<" !"<<std::endl;
		return false;
	}
	FILE* pFile;
	pFile = fopen(	fileName, "rb");
	if(!pFile) return false;
	fread(m_heightData.ucData, 1, terrainSize*terrainSize, pFile);
	fclose(pFile);

	m_heightData.iSize = terrainSize;
	m_iSize = m_heightData.iSize;

	std::cout<<"Loaded "<<fileName<<" ok!"<<std::endl;
	return true;
}

bool CHeightMapTerrain::ClearTerrainData()
{
	delete[] m_heightData.ucData;
	m_heightData.iSize = 0;
	return true;
}

bool CHeightMapTerrain::SaveTex( const char* fileName )
{
	if(m_texMgr)
	{
		return m_texMgr->SaveImg(fileName);
	}
	return false;
}

bool CHeightMapTerrain::SaveData( const char* fileName )
{
	assert(0 && "CHeightMapTerrain didn't implement SaveTex() methods!");
	return false;
}

void CHeightMapTerrain::SetHeight( int x, int z, float val )
{
	m_heightData.ucData[(z*m_iSize) + x] = (unsigned char)val;
}

float CHeightMapTerrain::GetRawHeight( int x, int z )
{
	return m_heightData.ucData[(z*m_iSize) + x];
}

float CHeightMapTerrain::GetScaleHeight( int x, int z )
{
	return m_heightData.ucData[(z*m_iSize) + x]*m_fScale;
}




CFaultInfomationTerrain::CFaultInfomationTerrain():m_fTerrainBuff(0)
{
	m_iSize = 0;
	m_fScale = 1;
	m_texMgr = new TexMgr_Ogre();
}

CFaultInfomationTerrain::~CFaultInfomationTerrain()
{
	ClearTerrainData();
	if(m_texMgr)
		delete m_texMgr;
}

bool CFaultInfomationTerrain::InitTerrainData( const char* fileName, int terrainSize )
{
	ClearTerrainData();
	m_fTerrainBuff = new float[terrainSize*terrainSize];
	//memset(m_fTerrainBuff, 0, terrainSize*terrainSize*sizeof(float));
	for(int idx = 0; idx < terrainSize*terrainSize; ++idx)
		m_fTerrainBuff[idx] = .0f;
	m_iSize = terrainSize;
	int iterNum = 1;
	for(int i = 0; i < TIMES_OF_ITER; ++i)
	{
		FaultInfoTerrain(iterNum++);
	}

	FilterTerrain(0.7f);

	NormTerrain(m_fTerrainBuff, terrainSize*terrainSize);

	//generate procedural texture
	m_texMgr->LoadImg("lowestTile.tga");
	m_texMgr->LoadImg("lowTile.tga");
	m_texMgr->LoadImg("HighTile.tga");
	m_texMgr->LoadImg("highestTile.tga");


	m_texMgr->SaveImg("GenPic.bmp");  //to view the result

	return true;
}

bool CFaultInfomationTerrain::ClearTerrainData()
{
	if(m_fTerrainBuff)
	{
		delete[] m_fTerrainBuff;
	}
	m_iSize = 0;
	return true;
}

void CFaultInfomationTerrain::FaultInfoTerrain( int iterNums )
{
	int rX1 = GetRandomRange(0, 255);
	int rZ1 = GetRandomRange(0, 255);
	int rX2 = GetRandomRange(0, 255);
	int rZ2 = GetRandomRange(0, 255);

	int iDirX1 = rX2 - rX1;
	int iDirZ1 = rZ2 - rZ1;

	int iDirX2, iDirZ2;

	float fHeight = (float)MAX_DELTA - (float)((MAX_DELTA - MIN_DELTA)*iterNums)/TIMES_OF_ITER;

	for(int x = 0; x <m_iSize; ++x)
	{
		for(int z = 0; z < m_iSize; ++z)
		{
			iDirX2 = x - rX1;
			iDirZ2 = z - rZ1;

			if((iDirX2*iDirZ1 - iDirZ2*iDirX1)>0)
			{
				m_fTerrainBuff[z*m_iSize + x] += fHeight;
			}
		}
	}
}


void CFaultInfomationTerrain::FilterTerrain(float eroVal)
{
	// Src--->dest
	for(int i = 0; i < m_iSize; ++i)
		FilterTerrainBand(&m_fTerrainBuff[i*m_iSize], 1, m_iSize, eroVal);
	//dest<---Src
	for(int i = 0; i < m_iSize; ++i)
		FilterTerrainBand(&m_fTerrainBuff[i*m_iSize + m_iSize - 1], -1, m_iSize, eroVal);
	//src VVV dest
	for(int i = 0; i < m_iSize; ++i)
		FilterTerrainBand(&m_fTerrainBuff[i], m_iSize, m_iSize, eroVal);
	//src ^^^ dest
	for (int i = 0; i < m_iSize; ++i)
		FilterTerrainBand(&m_fTerrainBuff[m_iSize*(m_iSize-1) + i], -m_iSize, m_iSize, eroVal);
}

bool CFaultInfomationTerrain::SaveTex( const char* fileName )
{
	if(m_texMgr)
	{
		return m_texMgr->SaveImg(fileName);
	}
	return false;
}

bool CFaultInfomationTerrain::SaveData( const char* fileName )
{
	assert(0 && "CHeightMapTerrain didn't implement SaveTex() methods!");
	return false;
}


void CFaultInfomationTerrain::SetHeight( int x, int z, float val )
{
	m_fTerrainBuff[(z*m_iSize) + x] = val;
}

float CFaultInfomationTerrain::GetRawHeight( int x, int z )
{
	return m_fTerrainBuff[(z*m_iSize) + x];
}

float CFaultInfomationTerrain::GetScaleHeight( int x, int z )
{
	return m_fTerrainBuff[(z*m_iSize) + x]*m_fScale;
}

void CFaultInfomationTerrain::FilterTerrainBand(float* startBuff, int stride, int count, float erosion)
{
	float val = startBuff[0];
	int dis = stride;				//warn! this func may destory memory tailed with buffer with arbitrary value!!!
	for(int i = 0; i<count - 1; ++i)
	{
		startBuff[dis] = val*erosion + startBuff[dis]*(1.0f - erosion);
		val = startBuff[dis];
		dis += stride;
	}
}

float CFaultInfomationTerrain::InterpolateHeight( int ix, int iz, float fH2tRadio )
{
	float scaleX = ix*fH2tRadio;
	float scaleZ = iz*fH2tRadio;
	
	float heigh = 0;

	float low = GetRawHeight(scaleX, scaleZ);
	if((scaleX+1) > (m_iSize - 1))
		return low;
	else
		heigh = GetRawHeight(scaleX+1, scaleZ);
	
	float itpltX = ((scaleX - (int)scaleX)*(heigh - low)) + low;

	if((scaleZ+1) > m_iSize - 1)
		return low;
	else
		heigh = GetRawHeight(scaleX, scaleZ + 1);

	float itpltY = ((scaleZ - (int)scaleZ)*(heigh - low)) + low;

	return (itpltX + itpltY)*0.5f;
}
